﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace WorldOnFire
{
    public abstract class ABCEnemy : ABCMovingObject
    {
        #region Target

        protected static CTarget s_Target;

        public static void SetTarget(CTarget _Target)
        { s_Target = _Target; }

        protected int[] m_nTargetIndex;

        #endregion Target

        #region Enemy Data

        public enum eEnemyType
        {
            BOMBER,
            DETONATOR,
            GUARDIAN,
            FRIGATE,
            DESTROYER,
            CRUISER,
            BATTLESHIP,
            CARRIER,
            JUGGERNAUT,
            COUNT
        }

        // Enemy data
        protected eEnemyType m_eEnemyType;
        protected float m_fOrbitAngleLock;
        protected float m_fMinimumRange;
        protected float m_fMaximumRange;
        protected float m_fShotDelayPrimary;
        protected float m_fShotDelaySecondary;
        protected float m_fShotSpeed;
        protected float m_fDamage;
        protected float m_fAreaOfEffect;
        protected float m_fMaximumHealth;
        protected float[] m_fHealth;

        // Movement data
        protected float m_fEngineForce;
        protected float m_fMaximumSpeed;
        protected float m_fTurnSpeed;

        // Rendering data
        private CRenderingManager.eTextureID m_eIlluminationTextureID;

        #endregion Enemy Data

        #region AI Data

        public enum eAIState
        {
            IDLE,
            CLOSING,
            ATTACKING,
            DISABLED,
            COUNT
        }

        private eAIState[] m_eAIState;
        protected float[] m_fAIStateTimer;

        protected float[] m_fDisabledTimer;
        protected float[] m_fShotTimerPrimary;
        protected float[] m_fShotTimerSecondary;
        protected Vector3[] m_vOrbitVector;

        public void SetAIState(int _nIndex, eAIState _eAIState)
        { m_eAIState[_nIndex] = _eAIState; m_fAIStateTimer[_nIndex] = 0.0f; }

        #endregion AI Data

        #region Accessors and Mutators

        public Vector3 GetOrbitVector(int _nIndex)
        { return m_vOrbitVector[_nIndex]; }

        public float GetRange()
        { return m_fMinimumRange; }

        public float GetDamage()
        { return m_fDamage; }

        public float GetAreaOfEffect()
        { return m_fAreaOfEffect; }

        public float GetShotSpeed()
        { return m_fShotSpeed; }

        #endregion Accessors and Mutators

        #region Initialization

        public override void Initialize(int _nObjectCount)
        {
            // Initialize the arrays
            m_eAIState = new eAIState[_nObjectCount];
            m_nTargetIndex = new int[_nObjectCount];
            m_fDisabledTimer = new float[_nObjectCount];
            m_fShotTimerPrimary = new float[_nObjectCount];
            m_fShotTimerSecondary = new float[_nObjectCount];
            m_fAIStateTimer = new float[_nObjectCount];
            m_vOrbitVector = new Vector3[_nObjectCount];
            m_fHealth = new float[_nObjectCount];

            // Initialize the base members
            base.Initialize(_nObjectCount);

            // Set rendering data
            m_nTechniqueID = (int)CRenderingManager.eObjectTechnique.ILLUMINATION;
            m_bOverlay = true;

            // Initialize members
            for(int iObject = 0; iObject < _nObjectCount; ++iObject)
            {
                m_eAIState[iObject] = eAIState.IDLE;

                m_fRadius[iObject] = 5.0f;
                m_vOrbitVector[iObject] = new Vector3(s_Ron.NextFloat(-1.0f, 1.0f), s_Ron.NextFloat(-0.5f, 0.5f), 0.0f);
                m_vOrbitVector[iObject].Normalize();
            }
        }

        public override void LoadContent()
        {
            m_eTextureID = CRenderingManager.eTextureID.ENEMY;
            m_eIlluminationTextureID = CRenderingManager.eTextureID.ENEMY_ILLUMINATION;

            base.LoadContent();
        }

        public virtual int CreateEnemy(CWarpGate _WarpGate, int _nWarpGateIndex)
        {
            // If there is an unused enemy
            if (this.FindUnusedIndex() == true)
            {
                // Create the enemy
                this.CreateObject(m_nInactiveObjectIndex);

                // Initialize the enemy
                m_mTransform[m_nInactiveObjectIndex] = _WarpGate.GetTransform(_nWarpGateIndex);
                m_mTransform[m_nInactiveObjectIndex].Translation 
                    += m_mTransform[m_nInactiveObjectIndex].Right * s_Ron.NextFloat(-10.0f, 10.0f)
                + m_mTransform[m_nInactiveObjectIndex].Up * s_Ron.NextFloat(-15.0f, 15.0f)
                + m_mTransform[m_nInactiveObjectIndex].Forward * s_Ron.NextFloat(-50.0f, 50.0f);

                m_fHealth[m_nInactiveObjectIndex] = m_fMaximumHealth;

                // Return the index
                return m_nInactiveObjectIndex;
            }

            // Return an invalid index
            return -1;
        }

        public virtual int CreateEnemy(ABCEnemy _Enemy, int _nEnemyIndex)
        {
            // If there is an unused enemy
            if (this.FindUnusedIndex() == true)
            {
                // Create the enemy
                this.CreateObject(m_nInactiveObjectIndex);

                // Initialize the enemy
                m_mTransform[m_nInactiveObjectIndex] = _Enemy.GetTransform(_nEnemyIndex);
                m_mTransform[m_nInactiveObjectIndex].Translation
                    += m_mTransform[m_nInactiveObjectIndex].Right * s_Ron.NextFloat(-1.0f, 1.0f)
                + m_mTransform[m_nInactiveObjectIndex].Up * s_Ron.NextFloat(-2.5f, 5.0f)
                + m_mTransform[m_nInactiveObjectIndex].Forward * s_Ron.NextFloat(-1.0f, 1.0f);

                // Return the index
                return m_nInactiveObjectIndex;
            }

            // Return an invalid index
            return -1;
        }

        #endregion Initialization

        #region Update and Render

        public override void Update(float _fDeltaTime)
        {
            // Loop through each enemy
            for (int iEnemy = 0; iEnemy < m_nObjectCount; ++iEnemy)
            {
                // If the enemy is inactive
                if (m_bObjectActive[iEnemy] == false)
                {
                    // Skip this enemy
                    continue;
                }

                // Increment the AI timer
                m_fAIStateTimer[iEnemy] += _fDeltaTime;

                // Switch based on the AI state
                switch ((int)m_eAIState[iEnemy])
                {
                    // If the actor is idle
                    case (int)eAIState.CLOSING:
                        {
                            // Run the closing AI
                            this.AIClosing(iEnemy, _fDeltaTime);
                        }
                        break;
                    // If the actor is idle
                    case (int)eAIState.ATTACKING:
                        {
                            // Run the closing AI
                            this.AIAttacking(iEnemy, _fDeltaTime);
                        }
                        break;
                    // If the actor is idle
                    case (int)eAIState.DISABLED:
                        {
                            // Run the closing AI
                            this.AIDisabled(iEnemy, _fDeltaTime);
                        }
                        break;
                    // If the actor is idle
                    case (int)eAIState.IDLE:
                        {
                            // Run the closing AI
                            this.AIIdle(iEnemy, _fDeltaTime);
                        }
                        break;
                }
            }

            // Update the base members
            base.Update(_fDeltaTime);
        }

        public override void Render(GraphicsDevice _GraphicsDevice)
        {
            Effect EnemyEffect = s_RenderingManager.GetEffect(m_eShaderID);
            EnemyEffect.Parameters["g_IlluminationMap"].SetValue(s_RenderingManager.GetTexture(m_eIlluminationTextureID));

            base.Render(_GraphicsDevice);
        }

        public override bool RespondToCollision(int _nIndex, ABCObject _Object, int _nObjectIndex)
        {
            switch ((int)_Object.GetObjectType())
            {
                case (int)eObjectType.PROJECTILE_BULLET:
                case (int)eObjectType.PROJECTILE_FLAK:
                case (int)eObjectType.PROJECTILE_SHELL:
                case (int)eObjectType.PROJECTILE_LASER:
                    {
                        ABCProjectile Projectile = (ABCProjectile)_Object;
                        m_fHealth[_nIndex] -= Projectile.GetDamage(_nObjectIndex);
                        if (m_fHealth[_nIndex] < 0.0f)
                        {
                            this.ScheduleForDeactivation(_nIndex);
                        }
                    }
                    break;
            }

            return false;
        }

        #endregion Update and Render

        #region AI Functionality

        protected virtual void AIIdle(int _nIndex, float _fDeltaTime)
        {
            // Chill out
        }

        protected virtual void AIClosing(int _nIndex, float _fDeltaTime)
        {
            // Determine the vector between the enemy and its target
            Vector3 vTargetPosition = s_Target.GetTransform(m_nTargetIndex[_nIndex]).Translation;
            Vector3 vBetween = vTargetPosition - m_mTransform[_nIndex].Translation;

            // Calculate the distance to the target
            float fDistance = vBetween.Length();

            // If the target is now in range of all weapons
            if (fDistance < m_fMinimumRange)
            {
                // Change the AI state to attacking and bail out
                this.SetAIState(_nIndex, eAIState.ATTACKING);
                return;
            }

            // Determine the normalized direction to the target
            Vector3 vDirection = vBetween / fDistance;

            // Turn towards target
            this.TurnTowards(_nIndex, vTargetPosition, m_fTurnSpeed * _fDeltaTime, true);

            // Calculate how fast the ship is moving towards its target
            float fSpeedTowardsTarget = Vector3.Dot(vDirection, m_vVelocity[_nIndex]);
            float fSpeed = m_vVelocity[_nIndex].Length();

            // If the ship is not moving directly towards the target
            if (fSpeed > fSpeedTowardsTarget)
            {
                // Scale down the velocity based on the difference in speed
                m_vVelocity[_nIndex] -= _fDeltaTime * (fSpeed - fSpeedTowardsTarget) * Vector3.Normalize(m_vVelocity[_nIndex]);
            }

            // If the ship is totally going so fast AH FUCK
            if (fSpeed > m_fMaximumSpeed)
            {
                // Scale down the velocity
                m_vVelocity[_nIndex] = m_fMaximumSpeed * Vector3.Normalize(m_vVelocity[_nIndex]);
            }

            // If the ship is facing the target and has not reached it's maximum speed
            if (Vector3.Dot(vDirection, m_mTransform[_nIndex].Backward) > 0.0f)
            {
                // Apply thrust
                this.ApplyForce(_nIndex, _fDeltaTime * m_fEngineForce * m_mTransform[_nIndex].Backward);
            }

            ////////////////////////////////////////////////////////////////////////////////////////
            /* BEGIN: Shooting stuff =============================================================*/

            // If no weapons are in range
            if (fDistance > m_fMaximumRange)
            {
                // EJECT
                return;
            }

            // Increment the shot timer
            m_fShotTimerPrimary[_nIndex] -= _fDeltaTime;

            // If a shot is ready to be fired
            if (m_fShotTimerPrimary[_nIndex] < 0.0f)
            {
                // Reset the shot timer
                m_fShotTimerPrimary[_nIndex] += m_fShotDelayPrimary;

                // Fire a shot
                this.FirePrimary(_nIndex);
            }

            /* END: Shooting stuff ===============================================================*/
            ////////////////////////////////////////////////////////////////////////////////////////
        }

        protected virtual void AIOrbiting(int _nIndex, float _fDeltaTime)
        {
            // Determine the vector between the enemy and its target
            Vector3 vTargetPosition = s_Target.GetTransform(m_nTargetIndex[_nIndex]).Translation;
            Vector3 vBetween = vTargetPosition - m_mTransform[_nIndex].Translation;
            Vector3 vTargetDirection = Vector3.Normalize(vBetween);

            // If the enemy is totally going so fast in relation to the target AH FUCK
            float fSpeedTowardsTarget = Vector3.Dot(m_vVelocity[_nIndex], vTargetDirection);
            if(fSpeedTowardsTarget > 5.0f || fSpeedTowardsTarget < -5.0f)
            {
                // Slow down
                this.ApplyForce(_nIndex, -_fDeltaTime * fSpeedTowardsTarget * m_fEngineForce * vTargetDirection);
            }

            // If the enemy is about to fall out of orbit
            if (Vector3.Dot(vBetween, vTargetDirection) > 0.9f * m_fMinimumRange)
            {
                // Apply an inward force
                this.ApplyForce(_nIndex, _fDeltaTime * m_fEngineForce * vTargetDirection);
            }

            // If the enemy is about too close
            if (Vector3.Dot(vBetween, vTargetDirection) < 0.75f * m_fMinimumRange)
            {
                // Apply an inward force
                this.ApplyForce(_nIndex, -_fDeltaTime * m_fEngineForce * vTargetDirection);
            }

            // If it's time to apply an orbital force
            if(m_fAIStateTimer[_nIndex] > 0.0f)
            {
                if(m_fAIStateTimer[_nIndex] > 0.5f)
                {
                    // Reset the state timer
                    m_fAIStateTimer[_nIndex] = -0.5f;        

                    // If the angle from the enemy to the target is too steep
                    float fDotResult = Vector3.Dot(vTargetDirection, Vector3.Up);
                    if (fDotResult > m_fOrbitAngleLock)
                    {
                        // Make the Y component positive
                        m_vOrbitVector[_nIndex].Y = Math.Abs(m_vOrbitVector[_nIndex].Y);
                    }
                    else if(fDotResult < -m_fOrbitAngleLock)
                    {
                        // Make the Y component negative
                        m_vOrbitVector[_nIndex].Y = -Math.Abs(m_vOrbitVector[_nIndex].Y);
                    }
                }

                // Transform the orbit vector into world space
                Vector3 vOrbitVector = Vector3.TransformNormal(m_vOrbitVector[_nIndex], m_mTransform[_nIndex]);

                // Orbit to the right
                this.ApplyForce(_nIndex, _fDeltaTime * m_fEngineForce * vOrbitVector);
            }

            // If the enemy is totally orbiting so fast AH FUCK
            if (m_vVelocity[_nIndex].LengthSquared() > m_fMaximumSpeed * m_fMaximumSpeed)
            {
                // Slow down
                m_vVelocity[_nIndex] = m_fMaximumSpeed * Vector3.Normalize(m_vVelocity[_nIndex]);
                //this.ApplyForce(_nIndex, _fDeltaTime * m_fEngineForce * -Vector3.Normalize(m_vVelocity[_nIndex]));
            }
        }

        protected virtual void AIAttacking(int _nIndex, float _fDeltaTime)
        {
            // If the target is inactive
            if (m_bObjectActive[_nIndex] == false)
            {
                // Change the AI state to idle and bail out
                this.SetAIState(_nIndex, eAIState.IDLE);
                return;
            }

            // Orbit
            this.AIOrbiting(_nIndex, _fDeltaTime);

            // Determine the vector between the enemy and its target
            Vector3 vTargetPosition = s_Target.GetTransform(m_nTargetIndex[_nIndex]).Translation;
            Vector3 vBetween = vTargetPosition - m_mTransform[_nIndex].Translation;

            // Calculate the distance to the target
            float fDistance = vBetween.Length();

            // If the target is out of range of some weapons
            if (fDistance > m_fMinimumRange)
            {
                // Change the AI state to closing and bail out
                this.SetAIState(_nIndex, eAIState.CLOSING);
                return;
            }

            // Normalize the direction to the target
            vBetween /= fDistance;

            // Turn towards the target
            this.TurnTowards(_nIndex, vTargetPosition, _fDeltaTime * m_fTurnSpeed, true);

            ////////////////////////////////////////////////////////////////////////////////////////
            /* BEGIN: Shooting stuff =============================================================*/

            // Increment the shot timer
            m_fShotTimerPrimary[_nIndex] -= _fDeltaTime;
            m_fShotTimerSecondary[_nIndex] -= _fDeltaTime;

            // If a shot is ready to be fired
            if (m_fShotTimerPrimary[_nIndex] < 0.0f)
            {
                // Reset the shot timer
                m_fShotTimerPrimary[_nIndex] += m_fShotDelayPrimary;

                // Fire a shot
                this.FirePrimary(_nIndex);
            }

            // If a shot is ready to be fired
            if (m_fShotTimerSecondary[_nIndex] < 0.0f)
            {
                // Reset the shot timer
                m_fShotTimerSecondary[_nIndex] += m_fShotDelaySecondary;

                // Fire a shot
                this.FireSecondary(_nIndex);
            }

            /* END: Shooting stuff ===============================================================*/
            ////////////////////////////////////////////////////////////////////////////////////////
        }

        protected virtual void AIDisabled(int _nIndex, float _fDeltaTime)
        {
            // Drift and decrement the disabled timer
            m_fDisabledTimer[_nIndex] -= _fDeltaTime;

            // If the enemy has been disabled long enough
            if (m_fDisabledTimer[_nIndex] < 0.0f)
            {
                // Change the state to closing
                this.SetAIState(_nIndex, eAIState.CLOSING);
            }
        }

        protected abstract void FirePrimary(int _nIndex);

        protected virtual void FireSecondary(int _nIndex)
        {

        }

        #endregion AI Functionality
    }
}
